#include <QtCore/QtEndian>
#include <QtCore/QDateTime>
#include <QtCore/QDir>
#include <cstring>
#include "log_manager.h"
#include "log_manager_p.h"

namespace log {

LogManagerPrivate::LogManagerPrivate(const QString &file_path,
                                     const QString &file_prefix,
                                     int buffer_size,
                                     int queue_size,
                                     int write_time) :
  QThread(0),
  success_work(true),
  file(0),
  next_file_timestamp(-1),
  file_path(file_path),
  file_prefix(file_prefix),
  buffer(0),
  buffer_size(buffer_size),
  pos(0),
  write_time(write_time),
  queue_size(queue_size) {
  if(buffer_size < 1) {
    qFatal("Buffer size LogManager < 1");
  }

  if(write_time < 1) {
    qFatal("Write time LogManager < 1");
  }

  if(queue_size < 1) {
    qFatal("Queue size LogManager < 1");
  }

  buffer = new char[buffer_size];

  std::memset(buffer, 0, buffer_size);

  QObject::connect(this,
                   SIGNAL(started()),
                   SLOT(writeAllMessages()));

  moveToThread(this);
}

LogManagerPrivate::~LogManagerPrivate() {
  if(buffer) {
    delete [] buffer;
    buffer = 0;
  }
  if(!messages.isEmpty()) {
    qDeleteAll(messages);
    messages.clear();
  }
  destroyFile();
}

void LogManagerPrivate::addMessage(LogLevel level,
                                   int app,
                                   int cls,
                                   int obj,
                                   const QByteArray &msg) {
  locker.lock();
  int size(messages.size());
  locker.unlock();

  if(size == queue_size) {
    qWarning("Queue LogManager is full");
  } else {
    LogMessage *message = new LogMessage(QDateTime::currentMSecsSinceEpoch(),
                                         level,
                                         app,
                                         cls,
                                         obj,
                                         msg);
    locker.lock();
    messages.push_back(message);
    locker.unlock();
  }
}

void LogManagerPrivate::updateFile() {
  if(file) {
    if(QDateTime::currentMSecsSinceEpoch() > next_file_timestamp) {
      destroyFile();
      createFile();
    }
  } else {
    createFile();
  }
}

void LogManagerPrivate::createFile() {
  QDateTime now(QDateTime::currentDateTime());

  QDateTime next_day(now.addDays(1).date(), QTime(0, 0));

  next_file_timestamp = next_day.toMSecsSinceEpoch();

  QString file_name(file_prefix + now.toString("_dd_MM_yyyy") + ".log");

  if(!QDir::setCurrent(file_path)) {
    qFatal("Not set current log path %s", file_path.toStdString().data());
  }

  file = new QFile(file_name);

  if(!file->open(QIODevice::WriteOnly | QIODevice::Append)) {
    qFatal("Log file %s error open (%s)",
           file_name.toStdString().data(),
           file->errorString().toStdString().data());
  }
}

void LogManagerPrivate::destroyFile() {
  if(file) {
    if(file->isOpen()) {
      file->close();
    }
    delete file;
    file = 0;
    next_file_timestamp = -1;
  }
}

void LogManagerPrivate::writeFile() {
  if(pos == 0) return;

  if(file->isOpen()) {
    qint64 size(file->write(buffer, pos));
    file->flush();
    if(size != pos) {
      if(!file->waitForBytesWritten(5000)) {
        qFatal("Log file %s not written",
               file->fileName().toStdString().data());
      }
    }
    std::memset(buffer, 0, buffer_size);
    pos = 0;
  }
}

void LogManagerPrivate::write(int v) {
  v = qToBigEndian(v);

  char *c_pt(reinterpret_cast<char *>(&v));
  if(pos + 4 > buffer_size) {
    writeFile();
  }
  std::memcpy(buffer + pos, c_pt, 4);
  pos += 4;
}

void LogManagerPrivate::write(quint64 v) {
  v = qToBigEndian(v);

  char *c_pt(reinterpret_cast<char *>(&v));
  if(pos + 8 > buffer_size) {
    writeFile();
  }
  std::memcpy(buffer + pos, c_pt, 8);
  pos += 8;
}

void LogManagerPrivate::write(char v) {
  if(pos + 1 > buffer_size) {
    writeFile();
  }
  buffer[pos++] = v;
}

void LogManagerPrivate::write(const QString &v) {
  write(v.toUtf8() + '\0');
}

void LogManagerPrivate::write(const QByteArray &v) {
  int len(v.size());
  if(pos + len > buffer_size) {
    writeFile();
  }
  std::memcpy(buffer + pos, v.data(), len);
  pos += len;
}

void LogManagerPrivate::writeAllMessages() {
  while(1) {
    while(!queueIsEmpty()) {
      updateFile();
      locker.lock();
      LogMessage *message(messages.takeFirst());
      locker.unlock();
      quint64 timestamp(message->timestamp);
      write(timestamp);
      write(char(message->level));
      write(message->app);
      write(message->cls);
      write(message->obj);
      write(message->msg.size());
      write(message->msg);
      delete message;
    }
    writeFile();
    if(success_work) {
      msleep(write_time);
    } else if(queueIsEmpty()) {
      break;
    }
  }
}

bool LogManagerPrivate::queueIsEmpty() {
  locker.lock();
  bool res(messages.isEmpty());
  locker.unlock();
  return res;
}

LogManager::LogManager(const QString &file_path,
                       const QString &file_prefix,
                       int buffer_size,
                       int queue_size,
                       int write_time) :
  QObject(0),
  p(new LogManagerPrivate(
      file_path, file_prefix, buffer_size, queue_size, write_time)) {

  moveToThread(p);

  p->start();
}

LogManager::~LogManager() {
  if(p) {
    if(p->isRunning()) {
      p->success_work = false;
      p->quit();
      p->wait();
    }
    delete p;
    p = 0;
  }
}

void LogManager::debug(int app,
                       int cls,
                       int obj,
                       const QByteArray &msg) {
  p->addMessage(DEBUG, app, cls, obj, msg);
}

void LogManager::info(int app,
                      int cls,
                      int obj,
                      const QByteArray &msg) {
  p->addMessage(INFO, app, cls, obj, msg);
}

void LogManager::warning(int app,
                         int cls,
                         int obj,
                         const QByteArray &msg) {
  p->addMessage(WARNING, app, cls, obj, msg);
}

void LogManager::error(int app,
                       int cls,
                       int obj,
                       const QByteArray &msg) {
  p->addMessage(ERROR, app, cls, obj, msg);
}

void LogManager::fatal(int app,
                       int cls,
                       int obj,
                       const QByteArray &msg) {
  p->addMessage(FATAL, app, cls, obj, msg);
}

} // namespace log
